package com.alibaba.algo.components.permission.service;

import com.alibaba.algo.components.permission.dto.SysUserDTO;
import com.alibaba.algo.core.contant.SysConstant;
import com.alibaba.algo.core.exception.BaseException;
import com.alibaba.algo.util.DateUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Calendar;
import java.util.Date;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * Created by IntelliJ IDEA.
 * User: glorysongglory
 * Date: 2020/11/5
 * Time: 17:22
 */
@Component
public class TokenService {

    @Resource
    private RedisTemplate<String, String> permissionRedisTemplate;

    /**
     * 从httpservelet获取token
     *
     * @param httpServletRequest
     * @return
     */
    public String getTokenFromHttpServletRequest(HttpServletRequest httpServletRequest) {
        return httpServletRequest.getHeader(SysConstant.SYS_PERMISSION_JWT_TOKEN);
    }

    /**
     * 生成token
     *
     * @param subject
     * @param audience
     * @param sign
     * @param ttl      秒
     * @return
     */
    public String generateToken(String subject, String audience, String sign, int ttl) {
        String token = "";
        Date curDate = Calendar.getInstance().getTime();
        Date expireDate = DateUtil.addMinute(curDate, ttl);
        token = JWT.create().withSubject(subject)
                .withAudience(audience)
                .withExpiresAt(expireDate)
                .sign(Algorithm.HMAC256(sign));

        String tokenKey = RedisKeyService.getJwtTokenKey(token);
        //token hash
        permissionRedisTemplate.opsForHash().put(tokenKey, SysConstant.SYS_PERMISSION_JWT_TOKEN_SUBJECT_KEY, subject);
        permissionRedisTemplate.opsForHash().put(tokenKey, SysConstant.SYS_PERMISSION_JWT_TOKEN_AUDIENCE_KEY, audience);
        permissionRedisTemplate.opsForHash().put(tokenKey, SysConstant.SYS_PERMISSION_JWT_TOKEN_SIGN_KEY, sign);
        permissionRedisTemplate.expire(tokenKey, 120l, TimeUnit.MINUTES);

        return token;
    }

    /**
     * 从httpservletRequest获取userId
     *
     * @param httpServletRequest
     * @return
     */
    public Long getUserIdFromHttpServletRequest(HttpServletRequest httpServletRequest) {
        return getUserIdFromToken(getTokenFromHttpServletRequest(httpServletRequest));
    }

    /**
     * 从token转换为userId
     *
     * @param token
     * @return
     */
    public Long getUserIdFromToken(String token) {
        // 执行认证
        if (StringUtils.isEmpty(token)) {
            throw new BaseException(SysConstant.SYS_ERROR, "无token，请重新登录");
        }

        String tokenKey = RedisKeyService.getJwtTokenKey(token);
        if (!permissionRedisTemplate.hasKey(tokenKey)) {
            throw new BaseException(SysConstant.UNAUTHORIZED, "token过期，请重新登录");
        }

        Long userId;
        try {
            userId = Long.valueOf(permissionRedisTemplate.opsForHash().get(tokenKey, SysConstant.SYS_PERMISSION_JWT_TOKEN_USERID_KEY).toString());
        } catch (JWTDecodeException j) {
            throw new BaseException(SysConstant.SYS_ERROR, "token非法");
        }
        return userId;
    }

    /**
     * 校验token
     *
     * @param token
     * @return
     */
    public boolean checkToken(String token) {
        String tokenKey = RedisKeyService.getJwtTokenKey(token);
        Object signObj = permissionRedisTemplate.opsForHash().get(tokenKey, SysConstant.SYS_PERMISSION_JWT_TOKEN_SIGN_KEY);
        // 验证 token
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(String.valueOf(signObj))).build();
        try {
            jwtVerifier.verify(token);
        } catch (JWTVerificationException e) {
            throw new BaseException(SysConstant.SYS_ERROR, "token失效");
        }
        return true;
    }

    /**
     * 刷新token，后面考虑通过api 旧token换新token
     *
     * @param token
     * @param timeout
     * @param timeUnit
     */
    public void refreshToken(String token, long timeout, TimeUnit timeUnit) {
        String tokenKey = RedisKeyService.getJwtTokenKey(token);
        permissionRedisTemplate.expire(tokenKey, timeout, timeUnit);
    }

    /**
     * 初始化token对应的业务数据
     *
     * @param token
     * @param sysUserDTO
     */
    public void initTokenBizData(String token, SysUserDTO sysUserDTO ) {
        String userDataKey = RedisKeyService.getSysUserKey(sysUserDTO.getId());
        String userTokenKey = RedisKeyService.getSysUserTokenKey(sysUserDTO.getId());
        String tokenKey = RedisKeyService.getJwtTokenKey(token);
        permissionRedisTemplate.opsForHash().put(tokenKey, SysConstant.SYS_PERMISSION_JWT_TOKEN_USERID_KEY, String.valueOf(sysUserDTO.getId()));
        //userData hash
        permissionRedisTemplate.opsForHash().put(userDataKey, SysConstant.SYS_PERMISSION_USER_PASSWD, sysUserDTO.getPasswd());
        permissionRedisTemplate.expire(userDataKey, 7l, TimeUnit.DAYS);
        permissionRedisTemplate.opsForHash().put(userTokenKey, token, DateUtil.format(new Date()));
        permissionRedisTemplate.expire(userTokenKey, 7l, TimeUnit.DAYS);
    }

    /**
     * 全部下线
     *
     * @param userId
     */
    public void invalidToken(Long userId) {
        //找出userId对应的token
        String userTokenKey = RedisKeyService.getSysUserTokenKey(userId);
        Set<Object> tokenList = permissionRedisTemplate.opsForHash().keys(userTokenKey);
        if (null != tokenList && tokenList.size() > 0) {
            tokenList.stream().forEach(p -> {
                String tokenKey = RedisKeyService.getJwtTokenKey(String.valueOf(p));
                permissionRedisTemplate.delete(tokenKey);
            });
        }
        permissionRedisTemplate.delete(userTokenKey);
    }

    /**
     * 指定token下线
     *
     * @param token
     */
    public void invalidToken(String token) {
        Long userId = getUserIdFromToken(token);
        //找出userId对应的token
        String userTokenKey = RedisKeyService.getSysUserTokenKey(userId);
        Set<Object> tokenList = permissionRedisTemplate.opsForHash().keys(userTokenKey);
        if (null != tokenList && tokenList.size() > 0) {
            tokenList.stream().forEach(p -> {
                if (token.equals(String.valueOf(p))) {
                    String tokenKey = RedisKeyService.getJwtTokenKey(String.valueOf(p));
                    permissionRedisTemplate.delete(tokenKey);
                    permissionRedisTemplate.opsForHash().delete(userTokenKey, token);
                }
            });
        }
    }


}
